/**
 *  Copyright (c) 1997-2013, www.tinygroup.org (luo_guo@icloud.com).
 *
 *  Licensed under the GPL, Version 3.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *       http://www.gnu.org/licenses/gpl.html
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.tinygroup.jdbctemplatedslsession;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.NotWritablePropertyException;
import org.springframework.beans.PropertyAccessorFactory;
import org.springframework.dao.DataRetrievalFailureException;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.tinygroup.jdbctemplatedslsession.editor.AllowNullNumberEditor;
import org.tinygroup.jdbctemplatedslsession.keygenerator.AppKeyGenerator;
import org.tinygroup.jdbctemplatedslsession.keygenerator.DatabaseKeyGenerator;
import org.tinygroup.tinysqldsl.Insert;
import org.tinygroup.tinysqldsl.KeyGenerator;
import org.tinygroup.tinysqldsl.base.Column;
import org.tinygroup.tinysqldsl.base.InsertContext;
import org.tinygroup.tinysqldsl.insert.InsertBody;
import org.tinygroup.tinysqldsl.util.NamedParameterUtils;

import java.beans.PropertyDescriptor;
import java.util.*;

/**
 * 对象映射
 * 
 * @author renhui
 * 
 */
public class ObjectMapper {

	/** The class we are mapping to */
	private Class mappedClass;

	/** Map of the fields we provide mapping for */
	private Map mappedFields;

	/** Set of bean properties we provide mapping for */
	private Set mappedProperties;

	private SimpleDslSession dslSession;

	public ObjectMapper() {
	}

	public ObjectMapper(Class mappedClass) {
		initialize(mappedClass);
	}

	public Class getMappedClass() {
		return mappedClass;
	}

	public void setDslSession(SimpleDslSession dslSession) {
		this.dslSession = dslSession;
	}

	public void setMappedClass(Class mappedClass) {
		if (this.mappedClass == null) {
			initialize(mappedClass);
		} else {
			if (!this.mappedClass.equals(mappedClass)) {
				throw new InvalidDataAccessApiUsageException(
						"The mapped class can not be reassigned to map to "
								+ mappedClass
								+ " since it is already providing mapping for "
								+ this.mappedClass);
			}
		}
	}

	/**
	 * Initialize the mapping metadata for the given class.
	 * 
	 * @param mappedClass
	 *            the mapped class.
	 */
	protected void initialize(Class mappedClass) {
		this.mappedClass = mappedClass;
		this.mappedFields = new HashMap();
		this.mappedProperties = new HashSet();
		PropertyDescriptor[] pds = BeanUtils
				.getPropertyDescriptors(mappedClass);
		for (int i = 0; i < pds.length; i++) {
			PropertyDescriptor pd = pds[i];
			if (pd.getWriteMethod() != null) {
				this.mappedFields.put(pd.getName().toLowerCase(), pd);
				String underscoredName = underscoreName(pd.getName());
				if (!pd.getName().toLowerCase().equals(underscoredName)) {
					this.mappedFields.put(underscoredName, pd);
				}
				this.mappedProperties.add(pd.getName());
			}
		}
	}

	private String underscoreName(String name) {
		StringBuffer result = new StringBuffer();
		if (name != null && name.length() > 0) {
			result.append(name.substring(0, 1).toLowerCase());
			for (int i = 1; i < name.length(); i++) {
				String s = name.substring(i, i + 1);
				if (s.equals(s.toUpperCase())) {
					result.append("_");
					result.append(s.toLowerCase());
				} else {
					result.append(s);
				}
			}
		}
		return result.toString();
	}

	public <T> T assemble(boolean autoGeneratedKeys,
			TableMetaData tableMetaData, Insert insert) {
		Object mappedObject = BeanUtils.instantiateClass(mappedClass);
		BeanWrapper bw = PropertyAccessorFactory
				.forBeanPropertyAccess(mappedObject);
		initBeanWrapper(bw);
		InsertContext newContext = insert.getContext().copyContext();
		List<ColumnMetaData> columnMetaDatas = tableMetaData
				.getColumnMetaDatas();
		for (ColumnMetaData columnMetaData : columnMetaDatas) {
			String column = columnMetaData.getParameterName().toLowerCase();
			PropertyDescriptor pd = (PropertyDescriptor) this.mappedFields
					.get(column);
			if (pd != null) {
				try {
					Object value = null;
					if (isPrimaryColumn(column, tableMetaData)) {
						value = getPrimaryValue(autoGeneratedKeys, column,
								tableMetaData, insert);
						if (!autoGeneratedKeys) {// 数据库生成方式已经在keygenerator中执行数据库插入操作,不需要在插入上下文增加主键值
							Column primaryColumn = new Column(
									newContext.getTable(), column);
							newContext.addValues(primaryColumn.value(value));
						}
					} else {
						value = getColumnValue(column, tableMetaData,
								newContext);
					}
					bw.setPropertyValue(pd.getName(), value);
				} catch (NotWritablePropertyException ex) {
					throw new DataRetrievalFailureException(
							"Unable to map column " + column + " to property "
									+ pd.getName(), ex);
				}

			}
		}
		if (!autoGeneratedKeys) {// 执行数据库插入操作
			InsertBody insertBody = newContext.createInsert();
			String sql = NamedParameterUtils.substituteNamedParameters(
					insertBody.toString(), insert.mapValue());
			dslSession.getJdbcTemplate().update(sql,
					newContext.getParamValues());
		}
		return (T) bw.getWrappedInstance();
	}

	/**
	 * 获取非主键字段的值
	 * 
	 * @param tableMetaData
	 * @param insert
	 * @return
	 */
	private Object getColumnValue(String columnName,
			TableMetaData tableMetaData, InsertContext insertContext) {
		Object value = insertContext.getParamValue(columnName);
		if (value == null) {
			value = tableMetaData.getDefaultValue(columnName);
		}
		return value;
	}

	/**
	 * 判断是不是主键字段
	 * 
	 * @param column
	 * @param tableMetaData
	 * @return
	 */
	private boolean isPrimaryColumn(String columnName,
			TableMetaData tableMetaData) {
		return tableMetaData.isKeyName(columnName);
	}

	private Object getPrimaryValue(boolean autoGeneratedKeys, String keyName,
			TableMetaData tableMetaData, Insert insert) {
		InsertContext context = insert.getContext();
		KeyGenerator keyGenerator = null;
		if (autoGeneratedKeys) {
			keyGenerator = new DatabaseKeyGenerator(
					dslSession.getJdbcTemplate(), insert, tableMetaData);
		} else {
			keyGenerator = context.getTable().getGeneratorMap().get(keyName);
			if (keyGenerator == null) {
				keyGenerator = new AppKeyGenerator(dslSession.getIncrementer());
			}
		}
		return keyGenerator.generate(context);
	}

	/**
	 * Initialize the given BeanWrapper to be used for row mapping. To be called
	 * for each row.
	 * <p>
	 * The default implementation is empty. Can be overridden in subclasses.
	 * 
	 * @param bw
	 *            the BeanWrapper to initialize
	 */
	protected void initBeanWrapper(BeanWrapper bw) {
		bw.registerCustomEditor(byte.class, new AllowNullNumberEditor(
				Byte.class, true));
		bw.registerCustomEditor(short.class, new AllowNullNumberEditor(
				Short.class, true));
		bw.registerCustomEditor(int.class, new AllowNullNumberEditor(
				Integer.class, true));
		bw.registerCustomEditor(long.class, new AllowNullNumberEditor(
				Long.class, true));
		bw.registerCustomEditor(float.class, new AllowNullNumberEditor(
				Float.class, true));
		bw.registerCustomEditor(double.class, new AllowNullNumberEditor(
				Double.class, true));
	}

}
